#include <cstddef>
#include <iostream>

#ifdef _WIN32
#include "stdafx.h"
#include <windows.h>
#else
#include <dlfcn.h>
#endif

#include "../../SDK/include/Events.h"

#include "iprojectplugin.h"
#include "projecteventbus.h"
#include "projectevent.h"
#include "projectui.h"
#include "projectcfgdialog.h"
#include "outputdlg.h"

#include "projecteventhandler.h"

ProjectEventHandler* ProjectEventHandler::m_self = NULL;

ProjectEventHandler* ProjectEventHandler::self()
{
    if ( !m_self )
        m_self = new ProjectEventHandler;
    return m_self;
}

ProjectEventHandler::ProjectEventHandler()
    : d(ProjectPrivate::self()),
      m_bus(ProjectEventBus::self())
{
    LoadProjectPlugin( "./plugins/frame/project/cmakeprojectmanager.dll" );
    LoadProjectPlugin( "./plugins/frame/project/cineprojectmanager.dll" );
    LoadProjectPlugin( "./plugins/frame/project/msvcprojectcompiler.dll" );
}

ProjectEventHandler::~ProjectEventHandler()
{
    ReleaseProjectPlugin( "./plugins/frame/project/cmakeprojectmanager.dll" );
    ReleaseProjectPlugin( "./plugins/frame/project/cineprojectmanager.dll" );
    ReleaseProjectPlugin( "./plugins/frame/project/msvcprojectcompiler.dll" );
    delete d;
    delete m_bus;
}

bool ProjectEventHandler::HandleEvent( Events::Event* e )
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
    d = ProjectPrivate::self();
    switch ( e->GetType() ) {
        case Events::Command: {
            Events::CommandEvent* ce = (Events::CommandEvent*)e;
            if ( ce->cmd == cmd_import ) {
                CFileDialog dlg( TRUE );
                dlg.DoModal();
                CString projectfilepath = dlg.GetPathName();
                CT2CA pszConvertedAnsiString( projectfilepath );
                std::string str( pszConvertedAnsiString );
                d->project_mainfile = str;

                /// try to load the proper sub plugin
//                 std::string pluginfile;
//                 CString ext = dlg.GetFileExt();
//                 if ( ext == "TXT" || ext == "txt" ) {
//                     pluginfile = "./plugins/frame/project/cmakeprojectmanager.dll";
//                 }
//                 else if ( ext == "CINEPROJECT" || ext == "cineprojrct" ) {
//                     pluginfile = "./plugins/frame/project/cineprojectmanager.dll";
//                 }
//                 else {
//                     std::cerr << "not implemented yet.\n";
//                     break;
//                 }
//                 LoadProjectPlugin( pluginfile );

                ///                 delete dataevent;
                ProjectImportEvent* dataevent = new ProjectImportEvent;
                dataevent->d = d;
                m_bus->PublishEvent( dataevent );
                delete dataevent;
                std::cerr << ".......................\n";

//                 ReleaseProjectPlugin( pluginfile );
                std::cerr << "import project: " << str << '\n';
            }
            else if ( ce->cmd == cmd_export ) {
                CFileDialog dlg( FALSE );
                dlg.DoModal();
                CString projectfilepath = dlg.GetPathName();
                CT2CA pszConvertedAnsiString( projectfilepath );
                std::string str( pszConvertedAnsiString );
                d->project_mainfile = str;

                /// try to load the proper sub plugin
//                 std::string pluginfile;
//                 CString ext = dlg.GetFileExt();
//                 if ( ext == "TXT" || ext == "txt" ) {
//                     pluginfile = "./plugins/frame/project/cmakeprojectmanager.dll";
//                 }
//                 else if ( ext == "CINEPROJECT" || ext == "cineproject" ) {
//                     pluginfile = "./plugins/frame/project/cineprojectmanager.dll";
//                 }
//                 else {
//                     std::cerr << "not implemented yet.\n";
//                     break;
//                 }                
//                 LoadProjectPlugin( pluginfile );

                ProjectExportEvent* cmddataevent = new ProjectExportEvent;
                cmddataevent->d = d;
                m_bus->PublishEvent( cmddataevent );
                delete cmddataevent;

//                 ReleaseProjectPlugin( pluginfile );
            }
            else if ( ce->cmd == cmd_build ) {
                std::cerr << "build event.\n";
                ProjectBuildEvent* cmddataevent = new ProjectBuildEvent;
                cmddataevent->d = d;
                m_bus->PublishEvent( cmddataevent );
                delete cmddataevent;

                OutputDlg dlg;
                dlg.RunScriptCmd( "cc.bat" );
                dlg.DoModal();
            }
            else if ( ce->cmd == cmd_clean ) {
                ProjectCleanEvent* cmddataevent = new ProjectCleanEvent;
                cmddataevent->d = d;
                m_bus->PublishEvent( cmddataevent );
                delete cmddataevent;

                OutputDlg dlg;
                dlg.RunScriptCmd( "clean.bat" );
                dlg.DoModal();
            }
            else if ( ce->cmd == cmd_rebuild ) {
                ProjectRebuildEvent* cmddataevent = new ProjectRebuildEvent;
                cmddataevent->d = d;
                m_bus->PublishEvent( cmddataevent );
                delete cmddataevent;

                OutputDlg dlg;
                dlg.RunScriptCmd( "clean.bat; cc.bat" );
                dlg.DoModal();
            }
            else if ( ce->cmd == cmd_configure ) {
                ProjectCfgDialog dlg;
                dlg.DoModal();
            }
            else {
            }
            break;
        }
        #if 0
            /// building
        case Events::ProjectInstall:
            ((ProjectInstallEvent*)(e))->d = d;
            m_bus->PublishEvent( e );
            break;
        case Events::ProjectPrune:
            ((ProjectPruneEvent*)(e))->d = d;
            m_bus->PublishEvent( e );
            break;
        #endif
        default:
            std::cerr << "Unexpected Event Here!" << std::endl;
    }
    std::cout << "catch event" << std::endl;
    return false;
}

bool ProjectEventHandler::LoadProjectPlugin( const std::string& plugin_file )
{
    /// load
#ifdef _WIN32
    HINSTANCE lib = LoadLibraryA( plugin_file.c_str() );
    if ( !lib ) {
        std::cerr << "Cannot load library " << plugin_file << std::endl;
        return false;
    }
    /// load symbols
    create_project_plugin_t* create = (create_project_plugin_t*)GetProcAddress( lib, "CreateProjectPluginObject" );
    if ( !create ) {
        std::cerr << "Cannot load proc address: CreateProjectPluginObject" << std::endl;
        return false;
    }
#else
    void* lib = dlopen( plugin_file.c_str(), RTLD_LAZY );
    if ( !lib ) {
        std::cerr << "Cannot load library: " << dlerror() << std::endl;
        return false;
    }
    /// reset errors
    dlerror();
    /// load symbols
    create_project_plugin_t* create = (create_project_plugin_t*)dlsym( lib, "CreateProjectPluginObject" );
    const char* dlsym_error = dlerror();
    if ( dlsym_error ) {
        std::cerr << "Cannot load symbol create: " << dlsym_error << std::endl;
        return false;
    }
#endif

    /// create plugin instance
    IProjectPlugin* m = create();
    /// initialize plugin
    m->Initialize();
    /// register plugin events
    m->RegisterEvent( m_bus );

    /// store it
    plugin_t p = { m, lib };
    loaded_plugins.push_back( p );
    return true;
}

void ProjectEventHandler::ReleaseProjectPlugin( const std::string& plugin_file )/// FIXME: unload just one!
{
    for ( int i=0; i<loaded_plugins.size(); i++ ) {
        /// destroy plugin instance
        IProjectPlugin* p = loaded_plugins[i].p;
#ifdef _WIN32
        HINSTANCE lib = loaded_plugins[i].lib;
        destroy_project_plugin_t* destroy = (destroy_project_plugin_t*)GetProcAddress( lib, "DestroyProjectPluginObject" );
        if ( !destroy ) {
            std::cerr << "Cannot load proc address: DestroyProjectPluginObject" << std::endl;
            return;
        }
#else
        void* lib = loaded_plugins[i].lib;
        destroy_project_plugin_t* destroy = (destroy_project_plugin_t*)dlsym( lib, "DestroyProjectPluginObject" );
        const char* dlsym_error = dlerror();
        if ( dlsym_error ) {
            std::cerr << "Cannot load symbol destroy: " << dlsym_error << std::endl;
            return;
        }
#endif
        destroy( p );
        /// unload library
#ifdef _WIN32
        FreeLibrary( lib );
#else
        dlclose( lib );
#endif
    }
    loaded_plugins.clear();
}

/// file management
bool ProjectEventHandler::addFile( const std::string& file )
{
    d->project_targets[0].extracted_files.insert( file );
    return true;
}

bool ProjectEventHandler::renameFile( const std::string& orig, const std::string& replace )
{
    return removeFile( orig ) && addFile( replace );
}

bool ProjectEventHandler::removeFile( const std::string& file )
{
    d->project_targets[0].extracted_files.erase( file );
    return true;
}

bool ProjectEventHandler::removeFileOnDisk( const std::string& file )
{
    removeFile( file );
    /// FIXME: do the removing on file system
    return true;
}

bool ProjectEventHandler::addFolder( const std::string& folder, bool recursive )
{
    // FIXME:
    return true;
}

bool ProjectEventHandler::renameFolder( const std::string& orig, const std::string& replace )
{
    /// find the to-rename file entries
    std::vector<std::string> to_rename;
    std::set<std::string>::iterator it = d->project_targets[0].extracted_files.begin();
    std::set<std::string>::iterator end = d->project_targets[0].extracted_files.end();
    size_t pos;
    while ( it != end ) {
        pos = (*it).find( orig + '/' );
        if ( pos != std::string::npos ) {
            to_rename.push_back( *it );
            //            /// ok, got one, rename it!
            //             (*it).replace( pos, orig.size(), replace );
        }
        ++it;
    }
    /// do the renaming
    for ( int i=0; i<to_rename.size(); ++i ) {
        d->project_targets[0].extracted_files.erase( to_rename[i] );
        to_rename[i].replace( pos, orig.size(), replace );
        d->project_targets[0].extracted_files.insert( to_rename[i] );
    }
    /// FIXME: do the renaming on file system
    return true;
}

bool ProjectEventHandler::removeFolder( const std::string& folder )
{
    /// find the to-remove file entries
    std::vector<std::string> to_remove;
    std::set<std::string>::const_iterator it = d->project_targets[0].extracted_files.begin();
    std::set<std::string>::const_iterator end = d->project_targets[0].extracted_files.end();
    while ( it != end ) {
        if ( (*it).find( folder + '/' ) != std::string::npos )
            to_remove.push_back( *it );
        ++it;
    }
    /// do the removing
    for ( int i=0; i<to_remove.size(); ++i ) {
        d->project_targets[0].extracted_files.erase( to_remove[i] );
    }
    return true;
}

bool ProjectEventHandler::removeFolderOnDisk( const std::string& folder )
{
    removeFolder( folder );
    /// FIXME: do the removing on file system
    return true;
}

